package com.gcsoftware.web.service.elasticsearch.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gcsoftware.configs.AppConstants;
import com.gcsoftware.exception.ElasticsearchException;
import com.gcsoftware.web.beans.elasticsearchData.Count;
import com.gcsoftware.web.beans.elasticsearchData.Hit;
import com.gcsoftware.web.beans.elasticsearchData.QueryAggs;
import com.gcsoftware.web.beans.elasticsearchData.QueryHits;
import com.gcsoftware.web.service.elasticsearch.Repository;
import com.gcsoftware.web.service.elasticsearch.Transport;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ES Rest API 模板接入类
 * @author czp
 * date : 2018-04-10
 */
@Service
public class TemplateRepository<T> implements Repository<T> {
    private Logger logger = LoggerFactory.getLogger(TemplateRepository.class);

    @Value("#{'${elasticsearch.clusterNodes}'.split(',')}")
    private List<String> hosts = null;

    public TemplateRepository() {
        super();
    }

    @Override
    public Page<Hit<T>> queryDslByPage(String index, String type, String query, Pageable pageable, List<String> fields, String sort) {
        Page<Hit<T>> rPage;
        StringBuffer queryDsl = new StringBuffer();
        String url = "/" + index;
        try {
            if (StringUtils.isNoneBlank(type)) url += "/" + type + "/_search";
            else url += "/_search";
            int from = pageable.getPageNumber() * pageable.getPageSize();
            int size = pageable.getPageSize();
            if (query != null && query.length() > 0) {
                queryDsl.append("{\"from\":").append(from).append(",\"size\":").append(size);
                if (fields != null && fields.size() > 0) {
                    queryDsl.append(",\"_source\":[");
                    for (int i = 0; i < fields.size(); i++) {
                        if (i == fields.size() - 1) {
                            queryDsl.append("\"").append(fields.get(i)).append("\"]");
                            break;
                        }
                        queryDsl.append("\"").append(fields.get(i)).append("\",");
                    }
                }
                queryDsl.append(",\"query\":").append(query)
                        .append(",\"sort\":[{\"@timestamp\":\"" + sort +"\"}]")
                        .append("}");
                logger.info(" Query DSL : " + queryDsl.toString());
            }
            Map<String, String> paramMap = new HashMap<String, String>();
            paramMap.put("pretty", "true"); // 配置请求参数
            HttpEntity entity = new NStringEntity(queryDsl.toString(), ContentType.APPLICATION_JSON);
            RestClient client = Transport.getInstance(hosts).getClient();
            Response indexResponse = client.performRequest("POST", url, paramMap, entity);
            rPage = jsonToPage(indexResponse.getEntity(), pageable);
        } catch (IOException e) {
            return null;
        }
        return rPage;
    }

    @Override
    public List<Hit<T>> queryDslByList(String index, String type, String query, List<String> fields) {
        List<Hit<T>> rList;
        StringBuffer queryDsl = new StringBuffer();
        try {
            String url = "/" + index;
            if (StringUtils.isNotBlank(type)) url += "/" + type + "/_search";
            else url += "/_search";
            if (query != null && query.length() > 0) {
                queryDsl.append("{");
                if (fields != null && fields.size() > 0) {
                    queryDsl.append("\"_source\":[");
                    for (int i = 0; i < fields.size(); i++) {
                        if (i == fields.size() - 1) {
                            queryDsl.append("\"").append(fields.get(i)).append("\"],");
                            break;
                        }
                        queryDsl.append("\"").append(fields.get(i)).append("\",");
                    }
                }
                queryDsl.append("\"query\":").append(query).append(",\"from\":0,\"size\":10000}");
                logger.info(" Query DSL : " + queryDsl.toString());
            }
            Map<String, String> paramMap = new HashMap<String, String>();
            paramMap.put("pretty", "true");
            HttpEntity entity = new NStringEntity(queryDsl.toString(), ContentType.APPLICATION_JSON);
            RestClient client = Transport.getInstance(hosts).getClient();
            Response indexResponse = client.performRequest("POST", url, paramMap, entity);
            rList = jsonToList(indexResponse.getEntity());
        } catch (IOException e) {
            throw new ElasticsearchException(" List<Hit<T>> queryDslByList() : " + e.toString());
        }
        return rList;
    }

    @Override
    public Hit<T> queryDslById(String index, String type, String id, List<String> fields) {
        Hit<T> t;
        String url = "/" + index;
        StringBuffer queryDsl = new StringBuffer();
        try {
            if (StringUtils.isNoneBlank(type)) url += "/" + type + "/_search";
            else  url += "/_search";
            queryDsl.append("{\"query\":").append("{\"term\": {\"_id\":\"").append(id).append("\"}}");
            if (fields != null && fields.size() > 0) {
                queryDsl.append(",\"_source\":[");
                for (int i = 0; i < fields.size(); i++) {
                    if (i == fields.size() - 1) {
                        queryDsl.append("\"").append(fields.get(i)).append("\"]}");
                        break;
                    }
                    queryDsl.append("\"").append(fields.get(i)).append("\",");
                }
            } else {
                queryDsl.append("}");
            }
            logger.info(" Query DSL : " + queryDsl);
            HttpEntity entity = new NStringEntity(queryDsl.toString(), ContentType.APPLICATION_JSON);
            Map<String, String> paramMap = new HashMap<String, String>();
            paramMap.put("pretty", "true");
            RestClient client = Transport.getInstance(hosts).getClient();
            Response indexResponse = client.performRequest("POST", url, paramMap, entity);
            t = jsonToBean(indexResponse.getEntity());
        } catch (IOException e) {
            throw new ElasticsearchException(" Hit<T> queryDslById() " + e.toString());
        }
        return t;
    }

    @Override
    public T queryDslGroup(String index, String type, String query, String aggs, String group) {
        StringBuffer queryDsl = new StringBuffer();
        try {
            String url = "/" + index;
            if (StringUtils.isNotBlank(type)) url += "/" + type + "/_search";
            else url += "/_search";
            if (aggs != null && aggs.length() > 0) {
                queryDsl.append("{\"size\":0,\"aggs\":")
                        .append(aggs)
                        .append(",\"query\":")
                        .append(query)
                        .append("}");
                logger.info(" Query DSL : " + queryDsl.toString());
            } else {
                return null;
            }
            Map<String, String> paramMap = new HashMap<String, String>();
            paramMap.put("pretty", "true");
            HttpEntity entity = new NStringEntity(queryDsl.toString(), ContentType.APPLICATION_JSON);
            RestClient client = Transport.getInstance(hosts).getClient();
            Response indexResponse = client.performRequest("POST", url, paramMap, entity);
            return jsonToGroup(indexResponse.getEntity(), group);
        } catch (IOException e) {
            throw new ElasticsearchException(" Aggs<T> queryDslSqlCountGroup() : " + e.toString());
        }
    }

    @Override
    public long fieldCount(String index, String type, String query) {
        StringBuffer queryDsl = new StringBuffer();
        try {
            String url = "/" + index;
            if (StringUtils.isNotBlank(type)) url += "/" + type + "/_count";
            else url += "/_count";
            queryDsl.append("{\"query\":")
                    .append(query)
                    .append("}");
            logger.info(" Query DSL : " + queryDsl.toString());
            Map<String, String> paramMap = new HashMap<String, String>();
            paramMap.put("pretty", "true");
            HttpEntity entity = new NStringEntity(queryDsl.toString(), ContentType.APPLICATION_JSON);
            RestClient client = Transport.getInstance(hosts).getClient();
            Response indexResponse = client.performRequest("POST", url, paramMap, entity);
            return jsonToCount(indexResponse.getEntity());
        } catch (IOException e) {
            return 0;
        }
    }

    private long jsonToCount(HttpEntity entity) {
        String queryResult;
        ObjectMapper mapper = new ObjectMapper();
        try {
            queryResult = EntityUtils.toString(entity);
            Count results = mapper.readValue(queryResult, new TypeReference<Count>() {});
            return results.getCount();
        } catch (IOException e) {
            return 0;
        }
    }

    @SuppressWarnings("unchecked")
    private T jsonToGroup(HttpEntity entity, String group) {
        String queryResult;
        ObjectMapper mapper = new ObjectMapper();
        try{
            queryResult = EntityUtils.toString(entity);
            QueryAggs<T> results = mapper.readValue(queryResult, new TypeReference<QueryAggs<T>>(){});
            logger.info("<" + results.getAggregations() + ">");
            if (results.getAggregations() == null) return null;
            if (group.equals(AppConstants.Elasticsearch.ES_API_NAME_GROUPS.getValue())) {
                return (T) results.getAggregations().getEs_api_name_groups();
            } else if (group.equals(AppConstants.Elasticsearch.ES_APP_NAME_GROUPS.getValue())) {
                return (T) results.getAggregations().getEs_app_name_groups();
            } else if (group.equals(AppConstants.Elasticsearch.ES_ITEM_GROUPS.getValue())) {
                return (T) results.getAggregations().getEs_item_groups();
            } else {
                return null;
            }
        } catch (IOException e) {
            throw new ElasticsearchException(" T jsonToBean() " + e.toString());
        }
    }

    private Hit<T> jsonToBean(HttpEntity entity) {
        Hit<T> t;
        String queryResult;
        ObjectMapper mapper = new ObjectMapper();
        try{
            queryResult = EntityUtils.toString(entity);
            QueryHits<T> results = mapper.readValue(queryResult, new TypeReference<QueryHits<T>>(){});
            t = results.getHits().getHits().get(0);
        } catch (IOException e) {
            throw new ElasticsearchException(" T jsonToBean() " + e.toString());
        }
        return t;
    }

    private List<Hit<T>> jsonToList(HttpEntity entity) {
        String queryResult;
        List<Hit<T>> rList = new ArrayList<Hit<T>>();
        ObjectMapper mapper = new ObjectMapper();
        try {
            queryResult = EntityUtils.toString(entity);
            QueryHits<T> results = mapper.readValue(queryResult, new TypeReference<QueryHits<T>>() {});
            if (results != null && results.getHits().getHits().size() > 0) {
                List<Hit<T>> hits = results.getHits().getHits();
                rList.addAll(hits);
            }
        } catch (IOException e) {
            throw new ElasticsearchException(" List<Hit<T>> jsonToList() " + e.toString());
        }
        return rList;
    }

    private Page<Hit<T>> jsonToPage(HttpEntity entity, Pageable pageable) {
        String queryResult;
        Page<Hit<T>> rPage;
        List<Hit<T>> content = new ArrayList<Hit<T>>();
        ObjectMapper mapper = new ObjectMapper();
        try {
            queryResult = EntityUtils.toString(entity);
            QueryHits<T> results = mapper.readValue(queryResult, new TypeReference<QueryHits<T>>() {});
            if (results != null && results.getHits().getHits().size() > 0) {
                List<Hit<T>> hits = results.getHits().getHits();
                content.addAll(hits);
            }
            long total = results.getHits().getTotal();
            rPage = new PageImpl<Hit<T>>(content, pageable, total);
        } catch (IOException e) {
            throw new ElasticsearchException(" PageObject<Hit<T>> jsonToPage() : " + e.toString());
        }
        return rPage;
    }
}
